package net.entelijan.config.impl2;

import java.util.ArrayList;
import java.util.List;

import net.entelijan.cobean.bind.impl.BoundPropBinding;
import net.entelijan.cobean.core.ICobean;
import net.entelijan.cobean.core.ISingleSelectionListCobean;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.ClassUtils;

public abstract class AbstractBinderFactory implements IBinderFactory {

	private static Log log = LogFactory.getLog(AbstractBinderFactory.class);

	public AbstractBinderFactory() {
		super();
	}

	@Override
	public void addBinders(List<IBinder> binders,
			List<IEnhancedComponent> enhancedComponents) {
		List<IEnhancedComponent> coreComps = findCoreComponents(enhancedComponents);
		for (IEnhancedComponent coreComp : coreComps) {
			IBinder b = ceateBinder(coreComp, enhancedComponents);
			if (b != null) {
				binders.add(b);
			}
		}
	}

	private IBinder ceateBinder(final IEnhancedComponent coreComp,
			final List<IEnhancedComponent> allComps) {
		return new IBinder() {

			@Override
			public <T> T bind(T model, Object config) {
				return bindInternal(coreComp, allComps, model, config);
			}

		};
	}

	private <T> T bindInternal(IEnhancedComponent coreComp,
			List<IEnhancedComponent> allComps, T model, Object config) {
		T re = model;
		List<List<String>> adjPathes = adjustPathesOfCoreComponent(coreComp
				.getPathes());
		List<String> matchingPath = PathUtil.findMatchingPath(adjPathes, model
				.getClass());
		if (matchingPath != null && matchingPath.size() > 0) {
			log.info("binding to model path " + matchingPath);
			Class<?> propertyType = PathUtil.getPropertyType(model,
					matchingPath);
			ICobean<?> cobean = createCobean(coreComp, allComps, propertyType, config);
			if (cobean != null) {
				log.info("binding cobean with BoundPropBinding '"
						+ matchingPath + "'");
				String matchingPathString = PathUtil.pathToString(matchingPath);
				BoundPropBinding pb = new BoundPropBinding();
				pb.setCompProperty(getCobeanProperty());
				pb.setModelPath(matchingPathString);
				re = pb.bind(model, cobean);
				if (cobean instanceof ISingleSelectionListCobean<?>) {
					String modelPath = matchingPathString + "SelectedValue";
					re = bindSelectedValue(re, modelPath, cobean);
				} 
			}
		}
		return re;
	}

	protected <T> T bindSelectedValue(T model, String modelPath,
			ICobean<?> cobean) {
		return model;
	}

	protected String getCobeanProperty() {
		return "value";
	}

	private List<IEnhancedComponent> findCoreComponents(
			List<IEnhancedComponent> enhancedComponents) {
		List<IEnhancedComponent> re = new ArrayList<IEnhancedComponent>();
		for (IEnhancedComponent comp : enhancedComponents) {
			if (isCoreComponent(comp)) {
				re.add(comp);
			}
		}
		return re;
	}

	protected abstract List<List<String>> adjustPathesOfCoreComponent(
			List<List<String>> pathes);

	protected abstract ICobean<?> createCobean(IEnhancedComponent coreComp,
			List<IEnhancedComponent> allComps, Class<?> modelPropertyType, Object config);

	protected abstract boolean isCoreComponent(IEnhancedComponent comp);

	// Utillity methods to be used in subclasses

	protected boolean isMatchingComponent(IEnhancedComponent comp,
			Class<?> reqCompClass, String boundaryKey) {
		boolean re = false;
		if (ClassUtils.isAssignable(reqCompClass, comp.getComponent()
				.getClass())) {
			List<List<String>> pathes = comp.getPathes();
			for (List<String> path : pathes) {
				if (PathUtil.containsKeywordIgnoreCase(path, boundaryKey)) {
					re = true;
				}
			}
		}
		return re;
	}

	@SuppressWarnings("unchecked")
	protected <T> T findComponent(List<IEnhancedComponent> allComps,
			IEnhancedComponent coreComp, Class<T> compClass, String boundaryKey) {
		IEnhancedComponent generalComp = null;
		IEnhancedComponent namedComp = null;
		for (IEnhancedComponent comp : allComps) {
			if (isMatchingComponent(comp, (Class<?>) compClass, boundaryKey)) {
				List<List<String>> possibleNames = PathUtil.getHeads(comp
						.getPathes(), boundaryKey);
				if (possibleNames != null) {
					if (possibleNames.size() == 0) {
						if (generalComp != null) {
							log
									.warn("Found more than one general component for '"
											+ boundaryKey + "'");
						}
						generalComp = comp;
					} else if (PathUtil.hasMatchingHead(coreComp.getPathes(),
							possibleNames)) {
						if (namedComp != null) {
							log
									.warn("Found more than one named component for '"
											+ boundaryKey + "'");
						}
						namedComp = comp;
					}
				}
			}
		}
		T re = null;
		if (namedComp != null) {
			re = (T) namedComp.getComponent();
		} else if (generalComp != null) {
			re = (T) generalComp.getComponent();
		}
		return re;
	}

}
